import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:whph/core/application/features/tasks/commands/add_task_tag_command.dart';
import 'package:whph/core/application/features/tasks/services/abstraction/i_task_tag_repository.dart';
import 'package:whph/core/domain/features/tasks/task_tag.dart';
import 'package:acore/acore.dart';

import 'add_task_tag_command_test.mocks.dart';

@GenerateMocks([ITaskTagRepository])
void main() {
  late AddTaskTagCommandHandler handler;
  late MockITaskTagRepository mockTaskTagRepository;

  setUp(() {
    mockTaskTagRepository = MockITaskTagRepository();
    handler = AddTaskTagCommandHandler(taskTagRepository: mockTaskTagRepository);
  });

  group('AddTaskTagCommandHandler Tests', () {
    // Test successful addition of task tag
    test('should add task tag when tag does not exist', () async {
      // Arrange
      const taskId = 'task-1';
      const tagId = 'tag-1';
      final command = AddTaskTagCommand(taskId: taskId, tagId: tagId);

      when(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).thenAnswer((_) async => false);

      when(mockTaskTagRepository.add(any)).thenAnswer((_) async => Future.value());

      // Act
      final result = await handler(command);

      // Assert
      verify(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).called(1);
      verify(mockTaskTagRepository.add(argThat(
        predicate<TaskTag>((tag) => tag.taskId == taskId && tag.tagId == tagId),
      ))).called(1);
      // The ID will be generated by the real KeyHelper.generateStringId() method
      expect(result.id, isNotNull);
    });

    // Test duplicate tag prevention
    test('should throw BusinessException when task tag already exists', () async {
      // Arrange
      const taskId = 'task-1';
      const tagId = 'tag-1';
      final command = AddTaskTagCommand(taskId: taskId, tagId: tagId);

      when(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).thenAnswer((_) async => true);

      // Act & Assert
      expect(
        () => handler(command),
        throwsA(
          predicate((e) => e is BusinessException && e.message == 'Task tag already exists'),
        ),
      );
      verify(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).called(1);
      verifyNever(mockTaskTagRepository.add(any));
    });

    // Test error handling when repository throws an exception
    test('should propagate repository exceptions', () async {
      // Arrange
      const taskId = 'task-1';
      const tagId = 'tag-1';
      final command = AddTaskTagCommand(taskId: taskId, tagId: tagId);

      when(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).thenThrow(Exception('Database error'));

      // Act & Assert
      expect(
        () => handler(command),
        throwsException,
      );
      verify(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).called(1);
      verifyNever(mockTaskTagRepository.add(any));
    });

    // Test parameter validation (implicit through non-nullable parameters)
    test('should handle empty string parameters correctly', () async {
      // Arrange
      const taskId = '';
      const tagId = '';
      final command = AddTaskTagCommand(taskId: taskId, tagId: tagId);

      when(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).thenAnswer((_) async => false);

      when(mockTaskTagRepository.add(any)).thenAnswer((_) async => Future.value());

      // Act
      final result = await handler(command);

      // Assert
      verify(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).called(1);
      verify(mockTaskTagRepository.add(argThat(
        predicate<TaskTag>((tag) => tag.taskId == taskId && tag.tagId == tagId),
      ))).called(1);
      expect(result.id, isNotNull);
    });

    // Test interaction verification
    test('should correctly interact with repository methods', () async {
      // Arrange
      const taskId = 'task-1';
      const tagId = 'tag-1';
      final command = AddTaskTagCommand(taskId: taskId, tagId: tagId);

      when(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).thenAnswer((_) async => false);

      when(mockTaskTagRepository.add(any)).thenAnswer((_) async => Future.value());

      // Act
      final result = await handler(command);

      // Assert
      // Verify that the repository methods were called with correct parameters
      verify(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).called(1);
      verify(mockTaskTagRepository.add(argThat(
        predicate<TaskTag>((tag) => tag.taskId == taskId && tag.tagId == tagId),
      ))).called(1);

      // Verify that the result has a generated ID
      expect(result.id, isNotNull);
    });

    // Test creation of TaskTag entity with correct properties
    test('should create TaskTag entity with correct properties', () async {
      // Arrange
      const taskId = 'task-1';
      const tagId = 'tag-1';
      final command = AddTaskTagCommand(taskId: taskId, tagId: tagId);

      when(mockTaskTagRepository.anyByTaskIdAndTagId(taskId, tagId)).thenAnswer((_) async => false);

      when(mockTaskTagRepository.add(any)).thenAnswer((_) async => Future.value());

      // Act
      await handler(command);

      // Assert
      verify(mockTaskTagRepository.add(argThat(
        predicate<TaskTag>((tag) => tag.taskId == taskId && tag.tagId == tagId),
      ))).called(1);
    });
  });
}
